//--------------------------------------------------------------------------------
// This file is a portion of the D2D1Renderer.  It is distributed
// under the MIT License, available in the root of this distribution and 
// at the following URL:
//
// http://www.opensource.org/licenses/mit-license.php
//
// Copyright (c) Mirco Timmermann
//
//--------------------------------------------------------------------------------
#include "WindowContainer.h"

#include "..\Button.h"


WindowContainer::WindowContainer(int x, int y, int width, int height) : DisplayObjectContainer(x, y, width, height) {
	_label  = "";
	_draggable = false;
	_resizable = false;

	_fixX = false;
	_fixY = false;

	_resizableRaster = 0;
	_draggableRaster = 0;

	_outlineWidth = 0;

	_minWidth = -1; //default
    _minHeight = -1; //default
    _maxWidth = -1; //default
    _maxHeight = -1; //default

	_currentOverSide = -1;
    _currentSelectedSide = -1;

	displayObjectContainer_inner = 0;

	_tempLocalDragPoint = 0;
	_boundingRect = 0;

	_highlight = false;

	//
	_r_stroke = 0.9f;
	_g_stroke = 0.9f;
	_b_stroke = 0.9f;

	_r_fill = 0.5f;
	_g_fill = 0.5f;
	_b_fill = 0.5f;

	_button_container = 0;

#ifdef _DEBUG
	this->name = "D2::WindowContainer";
#endif

	Initialize();
}

WindowContainer::~WindowContainer(void) {
	if(_tempLocalDragPoint != 0) {
		delete _tempLocalDragPoint;
		_tempLocalDragPoint = 0;
	}

	if(_boundingRect != 0) {
		delete _boundingRect;
		_boundingRect = 0;
	}
}

void WindowContainer::Initialize() {
	//create inner container.
	displayObjectContainer_inner = new DisplayObjectContainer();
	displayObjectContainer_inner->_y = 20;
	displayObjectContainer_inner->_height = this->_height-20;
	DisplayObjectContainer::addChild(displayObjectContainer_inner);

	//
	//this.minimize_btn_ON_MOUSE_UP = this.minimize_btn.onMouseUp.bind(this.minimize_btn);
	//this.maximize_btn_ON_MOUSE_UP = this.maximize_btn.onMouseUp.bind(this.maximize_btn);
	//this.close_btn_ON_MOUSE_UP = this.close_btn.onMouseUp.bind(this.close_btn);
    
	//ShowButtons(true);
}

void WindowContainer::ShowButtons(bool enable) {
	if(enable) {
		if(_button_container == 0) {
			_button_container = CreateButtons();
			DisplayObjectContainer::addChild(_button_container);
		}
	} else {
		if(_button_container != 0) {
			_button_container->Destroy();
			_button_container = 0;
		}
	}
}

void WindowContainer::UpdateButtonPosition() {
	_button_container->_x = (float)_width - (float)_button_container->_width - 5;
}

DisplayObjectContainer* WindowContainer::CreateButtons() {
	DisplayObjectContainer* container = new DisplayObjectContainer();
    
	Button* minimize_btn = new Button(0, 0, 15, 15); //, 3, "resources/images/minimizeButton.png");
	//minimize_btn->disableBackground = true;
	container->addChild(minimize_btn);
        
	Button* maximize_btn = new Button(minimize_btn->_x + (float)minimize_btn->_width + 5, minimize_btn->_y, 15, 15); //, 3, "resources/images/maximizeButton.png");
	//maximize_btn.disableBackground = true;
	container->addChild(maximize_btn);
        
	Button* close_btn = new Button(maximize_btn->_x + (float)maximize_btn->_width + 5, maximize_btn->_y, 15, 15); //, 3, "resources/images/closeButton.png");
	//close_btn.disableBackground = true;
	container->addChild(close_btn);
        
	//this.minimize_btn = minimize_btn;
	//this.maximize_btn = maximize_btn;
	//this.close_btn = close_btn;
        
	return container;
}

void WindowContainer::SetLabel(std::string label) {
	_label = label;
}

void WindowContainer::SetBounds(float x, float y, float width, float height) {
	if(_boundingRect == 0) {
		_boundingRect = new Rect();
	}
	_boundingRect->_x = x;
	_boundingRect->_y = y;
	_boundingRect->_width = width;
	_boundingRect->_height = height;
}

void WindowContainer::RemoveBounds() {
	if(_boundingRect != 0) {
		delete _boundingRect;
		_boundingRect = 0;
	}
}

/* @Override
*
*/
bool WindowContainer::Draw(Direct2DContext* ctx, D2D1::Matrix3x2F* mtx) {
	bool ok = true;

	if(_button_container != 0) {
		UpdateButtonPosition();
	}

	DrawWindowBar(ctx);
	DrawWindow(ctx); //if(!this._minimized || !this._maximized) DrawWindow(ctx);
	
	ctx->save();
	ok = DisplayObjectContainer::Draw(ctx, mtx);
	ctx->restore();

	return ok;
}

/* @Override
*
*/
DisplayObject* WindowContainer::addChild(DisplayObject* displayObject) {
	return this->displayObjectContainer_inner->addChild(displayObject);
}

/* @Override
*
*/
DisplayObject* WindowContainer::getChildAt(unsigned int index) {
	return this->displayObjectContainer_inner->getChildAt(index);
}

/* @Override
*
*/
int WindowContainer::getNumChildren() {
	return this->displayObjectContainer_inner->getNumChildren();
}

/* @Override
*
*/
void WindowContainer::removeAllChildren(bool recursive, bool del) {
	this->displayObjectContainer_inner->removeAllChildren(recursive, del);


}

void WindowContainer::DrawWindowBar(Direct2DContext* ctx) {
	//ctx->_disableCamera = true;

//draw the bar.
	ctx->lineWidth = 1;

	float multiplier = 1.0f;
	if(_highlight) {
		multiplier = 1.5f;
	}
	
	ctx->SetStrokeStyle(_r_stroke*multiplier, _g_stroke*multiplier, _b_stroke*multiplier); //ctx->strokeStyle = "";
	ctx->SetFillStyle(_r_fill*multiplier, _g_fill*multiplier, _b_fill*multiplier); //ctx->fillStyle = "";

    ctx->rect(0, 0, (float)this->_width, 20); //this.DrawRoundRectTop(ctx, 0, 0, this.width, 20, 3); //(float)this->_height
    ctx->fill();
	ctx->stroke();

//draw the label.
	//ctx->font = "10pt Calibri";
	ctx->SetStrokeStyle(1, 1, 1);
	ctx->fillText(_label, 5, 2);

	//ctx->_disableCamera = false;
}

void WindowContainer::DrawWindow(Direct2DContext* ctx) {
	//ctx->_disableCamera = true;

//draw background.
	ctx->lineWidth = 1;

	float multiplier = 1.0f;
	if(_highlight) {
		multiplier = 1.5f;
	}

	ctx->SetStrokeStyle(_r_stroke*multiplier, _g_stroke*multiplier, _b_stroke*multiplier); //ctx->strokeStyle = "";
	ctx->SetFillStyle(_r_fill*multiplier, _g_fill*multiplier, _b_fill*multiplier); //ctx->fillStyle = "";
        
	ctx->rect(0, 20, (float)this->_width, (float)this->_height - 20); //this.DrawRoundRectTop(ctx, 0, 0, this.width, 20, 3); //(float)this->_height
    ctx->fill();
	ctx->stroke();

	//ctx->_disableCamera = false;
}

/* (hittest)
* checks if the outline of the window is hittten.
*/
bool WindowContainer::HitTestOutline(float x, float y)  {
	if(x < _outlineWidth || x > _width - _outlineWidth || y < _outlineWidth || y > _height - _outlineWidth) {
		return true;
	}
	return false;
}

/*
* returns the hitten area. (ausschlussverfahren)
* -1 -> no hit.
* 0 -> leftTop.
* 1 -> top.
* 2 -> rightTop.
* 3 -> right.
* 4 -> rightBottom.
* 5 -> bottom.
* 6 -> leftBottom.
* 7 -> left.
        |---|--------------------------|---|
        | 0 |           A_r 1          | 2 |
        |---|--------------------------|---|
        |   |                          |   |
        |A_l|         B_l/B_r          |C_l|
        | 7 |            -1            | 3 |
        |   |        a                  |   |
        |---|--------------------------|---|
        | 6 |           C_r 5          | 4 |
        |---|--------------------------|---|
*/
int WindowContainer::GetHittenArea(float x, float y) {
	bool A_l = false;
	bool B_l = false;
	bool C_l = false;
        
	//FROM LEFT TO RIGHT.
	if(x < _outlineWidth) { //Right
		//(A)leftTop, left, leftBottom
		A_l = true;
	} else {
		if(x > _width - _outlineWidth) { //Right
		//(C)rightTop, right, rightBottom
		C_l = true;
		} else {
			//(b)top, bottom
			B_l = true;
		}
	}
        
	//FROM TOP TO BOTTOM.
	if(y < _outlineWidth) { //Right
		//(A)leftTop, top, rightTop
		if(A_l) return 0;
		if(B_l) return 1;
		if(C_l) return 2;
	} else {
		if(y > _height - _outlineWidth) { //Right
			//(C)leftBottom, bottom, rightBottom
			if(A_l) return 6;
			if(B_l) return 5;
			if(C_l) return 4;
		} else {
			//(B)left, right
			if(A_l) return 7;
			if(C_l) return 3;
		}
	}
	return -1;
}

/* @Override
*
*/
void WindowContainer::OnMouseOut(const MouseEvent &e) {
	 //document.body.style.cursor = "default";
	this->_currentOverSide = -1;

	DisplayObjectContainer::OnMouseOut(e);
}

/* @Override
*
*/
void WindowContainer::OnMouseDown0(const MouseEvent &e) {
	this->_highlight = true;

	if(_tempLocalDragPoint == 0) {
		D2D1_POINT_2F point = this->globalToLocal(e._stageX, e._stageY);
		if(_tempLocalDragPoint != 0) {
			delete _tempLocalDragPoint;
			_tempLocalDragPoint = 0;
		}
        _tempLocalDragPoint = new Point2f(point.x, point.y);
    }
        
	if(!_resizable) return;
        
    _currentSelectedSide = _currentOverSide;

	DisplayObjectContainer::OnMouseDown0(e);
}

/* @Override
*
*/
void WindowContainer::OnMouseMove(const MouseEvent &e) {
	if(!this->_resizable) return; // || !this._maximized) return;
        
	D2D1_POINT_2F point = this->globalToLocal(e._stageX, e._stageY);

	if(this->HitTestOutline(point.x, point.y)) {
		int value = this->GetHittenArea(point.x, point.y);
		/*
        if(value == -1) document.body.style.cursor = "default";
        if(value == 0) document.body.style.cursor = "nw-resize";
        if(value == 1) document.body.style.cursor = "n-resize";
        if(value == 2) document.body.style.cursor = "ne-resize";
        if(value == 3) document.body.style.cursor = "e-resize";
        if(value == 4) document.body.style.cursor = "se-resize";
        if(value == 5) document.body.style.cursor = "s-resize";
        if(value == 6) document.body.style.cursor = "sw-resize";
        if(value == 7) document.body.style.cursor = "w-resize";
		*/
        _currentOverSide = value;
	} else {
		//document.body.style.cursor = "default";
		_currentOverSide = -1;
	}

	DisplayObjectContainer::OnMouseMove(e);
}

/* @Override
*
*/
void WindowContainer::OnMouseUp0(const MouseEvent &e) {
	this->_currentSelectedSide = -1;
	if(_tempLocalDragPoint != 0) {
		delete _tempLocalDragPoint;
		_tempLocalDragPoint = 0;
	}

	DisplayObjectContainer::OnMouseUp0(e);
}

/* @Override
*
*/
void WindowContainer::OnMouseLeave0(const MouseEvent &e) {
	this->_currentSelectedSide = -1;
	if(_tempLocalDragPoint != 0) {
		delete _tempLocalDragPoint;
		_tempLocalDragPoint = 0;
	}

	DisplayObjectContainer::OnMouseLeave0(e);
}

/* @Override
*
*/
void WindowContainer::OnMouseRaise0(const MouseEvent &e) {
	this->_currentSelectedSide = -1;
	if(_tempLocalDragPoint != 0) {
		delete _tempLocalDragPoint;
		_tempLocalDragPoint = 0;
	}

	DisplayObjectContainer::OnMouseRaise0(e);
}

/* @Override
*
*/
void WindowContainer::OnMouseDrag0(const MouseEvent &e) {
	//scaling.
    if(_currentSelectedSide != -1) { // && _maximized) {
		this->resize(e);
        return;
	}
        
	//dragging.
	if(_draggable) {
		this->drag(e);
		this->RepositionByBounds();
	}

	DisplayObjectContainer::OnMouseDrag0(e);
}

void WindowContainer::OnMouseDown1(const MouseEvent &e) {
	this->_highlight = true;

	DisplayObjectContainer::OnMouseDown1(e);
}

void WindowContainer::OnMouseUp1(const MouseEvent &e) {
	//this->_highlight = false;

	DisplayObjectContainer::OnMouseUp1(e);
}

void WindowContainer::OnMouseRaise1(const MouseEvent &e) {

	DisplayObjectContainer::OnMouseRaise1(e);
}

void WindowContainer::OnMouseLeave1(const MouseEvent &e) {

	DisplayObjectContainer::OnMouseLeave1(e);
}

/*
* prevent that the window get out of the bounds.
*/
void WindowContainer::RepositionByBounds() {
	if(this->_boundingRect != 0) {
		if(this->_x < this->_boundingRect->_x) {
			this->_x = this->_boundingRect->_x;
        }
		if((this->_x + this->_width) > (this->_boundingRect->_x + this->_boundingRect->_width)) {
			this->_x = (this->_boundingRect->_x + this->_boundingRect->_width) - this->_width;
		}
		if(this->_y < this->_boundingRect->_y) {
			this->_y = this->_boundingRect->_y;
		}
		if((this->_y + this->_height) > (this->_boundingRect->_y + this->_boundingRect->_height)) {
			this->_y = (this->_boundingRect->_y + this->_boundingRect->_height) - this->_height;
		}
	}
}

void WindowContainer::drag(const MouseEvent &e) {
	if(_draggableRaster > 1) {
		if(this->_parent != 0) {
			//for rastering that is going from the start position of the WindowContainer itself.
            D2D1_POINT_2F point = this->globalToLocal(e._stageX, e._stageY);

			float newX = ceil((point.x-_tempLocalDragPoint->x)/_draggableRaster)*_draggableRaster;
            float newY = ceil((point.y-_tempLocalDragPoint->y)/_draggableRaster)*_draggableRaster;

            D2D1_POINT_2F point2 = this->localToLocal(newX, newY, this->_parent);

			if(!_fixX) {
				this->_x = point2.x;
			}
			if(!_fixY) {
				this->_y = point2.y;
			}
		}
	} else {
		/*
		//D2D1_POINT_2F point = this->localToLocal(this->_x + e._distanceX, this->_y + e._distanceY, this->GetParent());
		//D2D1_POINT_2F point = this->globalToLocal(e._stageX + e._distanceX, e._stageY + e._distanceY);
		if(!_fixX) {
			this->_x = point.x;
		}
		if(!_fixY) {
			this->_y = point.y;
		}
		*/

		///*
		if(!_fixX) {
			this->_x += e._distanceX;
		}
		if(!_fixY) {
			this->_y += e._distanceY;
		}
		//*/
	}
}

void WindowContainer::resize(const MouseEvent &e) {
	D2D1_POINT_2F point = this->globalToLocal(e._stageX, e._stageY);
            
	int value = _currentSelectedSide;

	float newX = (float)this->_x;
    float newY = (float)this->_y;
    float newWidth = (float)this->_width;
    float newHeight = (float)this->_height;

	bool leftChanged = false;
	bool rightChanged = false;
	bool topChanged = false;
	bool bottomChanged = false;
        
	//left.
	if(value == 0 || value == 7 || value == 6) {
		newX = this->_x + point.x; //newX = this.x + event.distanceX;
		newWidth = this->_width - point.x; //newWidth = this.width - event.distanceX;

		if(this->_minWidth > 0) { //if has set a minWidth.
			float offset_min_x = this->_width - (point.x + this->_minWidth);
			if(offset_min_x < 0) newX += offset_min_x;
		}

		if(this->_maxWidth > 0) { //if has set a maxWidth.
			float offset_max_x = (point.x + this->_maxWidth) - this->_width;
			if(offset_max_x < 0) newX -= offset_max_x;
		}

		leftChanged = true;
	}   

	//top.
	if(value == 0 || value == 1 || value == 2) {
		newY = this->_y + point.y; //newY = this.y + event.distanceY;
		newHeight = this->_height - point.y; //newHeight = this.height - event.distanceY;

		if(this->_minHeight > 0) { //if has set a minHeight.
			float offset_min_y = this->_height - (point.y + this->_minHeight);
			if(offset_min_y < 0) newY += offset_min_y;
		}

		if(this->_maxHeight > 0) { //if has set a maxHeight.
			float offset_max_y = (point.y + this->_maxHeight) - this->_height;
			if(offset_max_y < 0) newY -= offset_max_y;
		}

		topChanged = true;
	}

	//bottom.
	if(value == 6 || value == 5 || value == 4) {
		newHeight = point.y; //newHeight = this.height + event.distanceY;

		bottomChanged = true;
	}

	//right.
	if(value == 2 || value == 3 || value == 4) {
		newWidth = point.x; //newWidth = this.width + event.distanceX;

		rightChanged = true;
	}

	//clamping to min and max size bounds.
	if(this->_minWidth > 0) { //if has set a minWidth.
		if(newWidth < this->_minWidth) {
			newWidth = this->_minWidth;
		}
	}
	if(this->_minHeight > 0) { //if has set a minHeight.
		if(newHeight < this->_minHeight) {
			newHeight = this->_minHeight;
		}
	}
	if(this->_maxWidth > 0) { //if has set a maxWidth.
		if(newWidth > this->_maxWidth) {
			newWidth = this->_maxWidth;
		}
	}
	if(this->_maxHeight > 0) { //if has set a maxHeight.
		if(newHeight > this->_maxHeight) {
			newHeight = this->_maxHeight;
		}
	}
        
	//clamping to rectangle bounds.
	if(this->_boundingRect != 0) {
		if(newX < this->_boundingRect->_x) {
			float temppX = newX;
			newX = this->_boundingRect->_x;
			newWidth += temppX - newX;
		}
            
		if((newX + newWidth) > (this->_boundingRect->_x + this->_boundingRect->_width)) {
			float diffWidth = (this->_boundingRect->_x + this->_boundingRect->_width) - (newX + newWidth);
                
			newWidth += diffWidth;
		}
            
		if(newY < this->_boundingRect->_y) {
			float temppY = newY;
			newY = this->_boundingRect->_y;
			newHeight += temppY - newY;
		}
            
		if((newY + newHeight) > (this->_boundingRect->_y + this->_boundingRect->_height)) {
			float diffHeight = (this->_boundingRect->_y + this->_boundingRect->_height) - (newY + newHeight);
                
			newHeight += diffHeight;
		}    
	}
        
        //rastering.
	if(rightChanged && this->_resizableRaster > 1) newWidth = ceil(newWidth/this->_resizableRaster)*this->_resizableRaster;
	if(bottomChanged && this->_resizableRaster > 1) newHeight = ceil(newHeight/this->_resizableRaster)*this->_resizableRaster;

	if(leftChanged && this->_resizableRaster > 1) {
		float regWidth = ceil(newWidth/this->_resizableRaster)*this->_resizableRaster;

            newX = this->localToGlobal(newWidth + (this->globalToLocal(newX, 0).x - regWidth), 0).x;
            newWidth = regWidth;
        }

	if(topChanged && this->_resizableRaster > 1) {
		float regHeight = ceil(newHeight/this->_resizableRaster)*this->_resizableRaster;

		newY = this->localToGlobal(0, newHeight + (this->globalToLocal(0, newY).y - regHeight)).y;
		newHeight = regHeight;
	}

        //
	float tempX = this->_x;
	float tempY = this->_y;
	float tempWidth = this->_width;
	float tempHeight = this->_height;

        //
	this->_x = (float)newX;
	this->_y = (float)newY;
	this->_width = (int)newWidth;
	this->_height = (int)newHeight;

	//this.UpdateParent();
	//this.UpdateContainerWidth(this->_width - tempWidth);
	//this.UpdateContainerHeight(this->_height - tempHeight);

	if(tempX != this->_x || tempY != this->_y) {
		//var evt1 = new Event("DRAG_POSITION_CHANGED", this);
		//this.dispatchEvent(evt1);
	}

	if(tempWidth != this->_width || tempHeight != this->_height) {
		//var evt2 = new Event("DRAG_SIZE_CHANGED", this, event);
		//this.dispatchEvent(evt2);
	}
}

void WindowContainer::OnFocusLost(const MouseEvent &e) {
	this->_highlight = false;
}

//////////////

void WindowContainer::Highlight(bool enable) {
	_highlight = enable;
}

void WindowContainer::FixX(bool fix) {
	_fixX = fix;
}

void WindowContainer::FixY(bool fix) {
	_fixY = fix;
}

/////

void WindowContainer::SetFgColor(float r, float g, float b) {
	_r_stroke = r;
	_g_stroke = g; 
	_b_stroke = b;
	
}

void WindowContainer::SetBgColor(float r, float g, float b) {
	_r_fill = r;
	_g_fill = g;
	_b_fill = b;
}